/*
 * Copyright 2007-2009 Perttu "celeron55" Ahola <celeron55@gmail.com>
 *
 * This file is part of jukumagic.
 *
 * jukumagic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jukumagic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with jukumagic.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#include "footprint.h"
#include "line.h"
#include "rect.h"
#include "arc.h"
#include "pin.h"
#include "jukuapp.h"

/*void Footprint::ChildChanged(ObjectChangeType type, JukuObject *obj)
{
	//Do nothing
}*/

Footprint::Footprint()
{
	//std::cout<<"Footprint"<<std::endl;
	m_side = TOP;
	//m_boardrect_block_area_is_valid = false;
	m_graphics.m_owner = this;
	m_block_area.m_owner = this;
	m_pins.m_owner = this;
}

Footprint::Footprint(Footprint &footprint)
{
	//std::cout<<"Footprint from footprint"<<std::endl;

	m_side = footprint.m_side;

	m_graphics = footprint.m_graphics;
	m_block_area = footprint.m_block_area;
	m_pins = footprint.m_pins;

	m_graphics.m_owner = this;
	m_block_area.m_owner = this;
	m_pins.m_owner = this;
	
	m_name = footprint.m_name;
	m_subname = footprint.m_subname;
}

Footprint::~Footprint()
{
	//std::cout<<"~Footprint"<<std::endl;
}

JukuObject* Footprint::MakeACopy()
{
    Footprint *footprint = new Footprint(*this);
    return footprint;
}

void Footprint::WriteXML(TiXmlNode* root)
{
    TiXmlElement *element = new TiXmlElement("Footprint");
    root->LinkEndChild(element);

    element->SetAttribute("name", m_name.mb_str(wxConvUTF8));
    element->SetAttribute("subname", m_subname.mb_str(wxConvUTF8));

    if(m_side == TOP) element->SetAttribute("side", "top");
	else if(m_side == BOTTOM) element->SetAttribute("side", "bottom");

	{
		TiXmlElement *child = new TiXmlElement("Graphics");
		element->LinkEndChild(child);
		m_graphics.WriteBaseXML(child);
	}
	{
		TiXmlElement *child = new TiXmlElement("BlockArea");
		element->LinkEndChild(child);
		m_block_area.WriteBaseXML(child);
	}
	{
		TiXmlElement *child = new TiXmlElement("Pins");
		element->LinkEndChild(child);
		m_pins.WriteBaseXML(child);
	}

	WriteBaseXML(element);
}

JukuObject* Footprint::ReadFromXMLElement(TiXmlElement* element)
{
    Footprint *footprint = new Footprint();
	
	const char *c;
	c = element->Attribute("name");
	if(c) footprint->m_name = wxString(c, wxConvUTF8);
	c = element->Attribute("subname");
	if(c) footprint->m_subname = wxString(c, wxConvUTF8);

	c = element->Attribute("side");
	if(c){
		std::string side_s = c;
		if(side_s == "top") footprint->m_side = TOP;
		else if(side_s == "bottom") footprint->m_side = BOTTOM;
	}
	
	footprint->m_graphics.ReadBaseXML(element->FirstChildElement("Graphics"));
	footprint->m_block_area.ReadBaseXML(element->FirstChildElement("BlockArea"));
	footprint->m_pins.ReadBaseXML(element->FirstChildElement("Pins"));

	footprint->ReadBaseXML(element);

	footprint->SimplifyCoordinates(NULL);

    return footprint;
}

#define DEFAULT_RECT_W 600
#define DEFAULT_RECT_H 200

void Footprint::Draw(wxDC &dc, wxWindow *window, wxPoint origin)
{
	//std::cout<<"Footprint::Draw()"<<std::endl;

	if(GetSelected()){
		wxPen pen(wxGetApp().m_settings.m_colour_selected,
				wxGetApp().m_settings.m_linewidth_footprint_graphics);
		dc.SetPen(pen);
	}
	else{
		wxPen pen(wxGetApp().m_settings.m_colour_footprint_graphics,
				wxGetApp().m_settings.m_linewidth_footprint_graphics);
		dc.SetPen(pen);
	}
	wxBrush brush(*wxBLACK, wxTRANSPARENT);
	dc.SetBrush(brush);
	
	for(std::list<JukuObject*>::const_iterator i = m_graphics.GetObjects().begin(); i != m_graphics.GetObjects().end(); i++){
		JukuObject *object = (*i);

		if(window){
			BoardRect br = object->GetDrawArea();
			wxRect update_rect(dc.LogicalToDeviceXRel(br.m_x + origin.x), dc.LogicalToDeviceYRel(br.m_y + origin.y), dc.LogicalToDeviceXRel(br.m_w), dc.LogicalToDeviceYRel(br.m_h));
			if (window->IsExposed(update_rect)){
				object->Draw(dc, window, origin);
			}
		}
		else{
			object->Draw(dc, NULL, origin);
		}
	}

	if(m_graphics.GetObjects().empty()){
		for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
			JukuObject *object = (*i);

			if(window){
				BoardRect br = object->GetDrawArea();
				wxRect update_rect(dc.LogicalToDeviceXRel(br.m_x + origin.x), dc.LogicalToDeviceYRel(br.m_y + origin.y), dc.LogicalToDeviceXRel(br.m_w), dc.LogicalToDeviceYRel(br.m_h));
				if (window->IsExposed(update_rect)){
					object->Draw(dc, window, origin);
				}
			}
			else{
				object->Draw(dc, NULL, origin);
			
			}
		}
	}
	
	if(m_graphics.GetObjects().empty() && m_block_area.GetObjects().empty()){
		dc.DrawRectangle(-DEFAULT_RECT_W/2 + origin.x, -DEFAULT_RECT_H/2 + origin.y,
				DEFAULT_RECT_W, DEFAULT_RECT_H);
		/*wxBrush brush(dc.GetPen().GetColour(), wxSOLID);
		dc.SetBrush(brush);*/
		dc.SetFont(wxGetApp().m_settings.m_font_board_text);
		dc.DrawText(wxT("INVALID\nFOOTPRINT"),
				-DEFAULT_RECT_W/2 + origin.x, -DEFAULT_RECT_H/2 + origin.y);
	}

	for(std::list<JukuObject*>::const_iterator i = m_pins.GetObjects().begin(); i != m_pins.GetObjects().end(); i++){
		JukuObject *object = (*i);

		if(window){
			BoardRect br = object->GetDrawArea();
			wxRect update_rect(dc.LogicalToDeviceXRel(br.m_x + origin.x), dc.LogicalToDeviceYRel(br.m_y + origin.y), dc.LogicalToDeviceXRel(br.m_w), dc.LogicalToDeviceYRel(br.m_h));
			if (window->IsExposed(update_rect)){
				object->Draw(dc, window, origin);
			}
		}
		else{
			object->Draw(dc, NULL, origin);
		}
	}

}

BoardRect Footprint::GetDrawArea()
{
	int x1=BIG_INT_VALUE, y1=BIG_INT_VALUE, x2=SMALL_INT_VALUE, y2=SMALL_INT_VALUE;

	if(m_graphics.GetObjects().empty() && m_block_area.GetObjects().empty()){
		return BoardRect(-DEFAULT_RECT_W/2-wxGetApp().m_settings.m_linewidth_footprint_graphics/2,
				-DEFAULT_RECT_H/2-wxGetApp().m_settings.m_linewidth_footprint_graphics/2,
				DEFAULT_RECT_W+wxGetApp().m_settings.m_linewidth_footprint_graphics,
				DEFAULT_RECT_H+wxGetApp().m_settings.m_linewidth_footprint_graphics);
	}

	for(std::list<JukuObject*>::const_iterator i = m_graphics.GetObjects().begin(); i != m_graphics.GetObjects().end(); i++){
		BoardRect br = (*i)->GetDrawArea();
		if(br.m_x < x1) x1 = br.m_x;
		if(br.m_y < y1) y1 = br.m_y;
		if(br.m_x + (int)br.m_w > x2) x2 = br.m_x + br.m_w;
		if(br.m_y + (int)br.m_h > y2) y2 = br.m_y + br.m_h;
	}
	
	if(m_graphics.GetObjects().empty()){
		for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
			BoardRect br = (*i)->GetDrawArea();
			if(br.m_x < x1) x1 = br.m_x;
			if(br.m_y < y1) y1 = br.m_y;
			if(br.m_x + (int)br.m_w > x2) x2 = br.m_x + br.m_w;
			if(br.m_y + (int)br.m_h > y2) y2 = br.m_y + br.m_h;
		}
	}

	for(std::list<JukuObject*>::const_iterator i = m_pins.GetObjects().begin(); i != m_pins.GetObjects().end(); i++){
		BoardRect br = (*i)->GetDrawArea();
		if(br.m_x < x1) x1 = br.m_x;
		if(br.m_y < y1) y1 = br.m_y;
		if(br.m_x + (int)br.m_w > x2) x2 = br.m_x + br.m_w;
		if(br.m_y + (int)br.m_h > y2) y2 = br.m_y + br.m_h;
	}

	return BoardRect(x1, y1, x2-x1, y2-y1);
}

/*void Footprint::MakeBoardRectBlockArea()
{
	m_boardrect_block_area.clear();
	for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
		if((*i)->GetType() == TYPE_RECT){
			m_boardrect_block_area.push_back(*((BoardRect*)((Rect*)(*i))));
		}
	}
	m_boardrect_block_area_is_valid = true;
}

std::list<BoardRect> Footprint::GetBlockArea()
{
	if(!m_boardrect_block_area_is_valid) MakeBoardRectBlockArea();
	return m_boardrect_block_area;
}*/

bool Footprint::IsSelectedInPoint(int x, int y)
{
	if(m_graphics.GetObjects().empty() && m_block_area.GetObjects().empty()){
		return (abs(x) < DEFAULT_RECT_W/2 && abs(y) < DEFAULT_RECT_H/2);
	}
	if(m_block_area.GetObjects().empty()){
		for(std::list<JukuObject*>::const_iterator i = m_graphics.GetObjects().begin(); i != m_graphics.GetObjects().end(); i++){
			if((*i)->GetType() == TYPE_RECT){
				BoardRect *r = (BoardRect*)((Rect*)(*i));
				if(x > r->m_x && x < r->m_x + (int)r->m_w && y > r->m_y && y < r->m_y + (int)r->m_h) return true;
			}
			if((*i)->GetType() == TYPE_ARC){
				JArc *arc = (JArc*)(*i);
				int r_max, t;
				r_max = sqrt(pow(arc->m_x1 - arc->m_xc, 2) + pow(arc->m_y1 - arc->m_yc, 2));
				t = sqrt(pow(arc->m_x2 - arc->m_xc, 2) + pow(arc->m_y2 - arc->m_yc, 2));
				if(t > r_max) r_max = t;
				BoardRect r = BoardRect(arc->m_xc-r_max, arc->m_yc-r_max, r_max*2, r_max*2);
				if(x > r.m_x && x < r.m_x + (int)r.m_w && y > r.m_y && y < r.m_y + (int)r.m_h) return true;
			}
		}
		return false;
	}
	for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
		if((*i)->GetType() == TYPE_RECT){
			BoardRect *r = (BoardRect*)((Rect*)(*i));
			if(x > r->m_x && x < r->m_x + (int)r->m_w && y > r->m_y && y < r->m_y + (int)r->m_h) return true;
		}
	}
	return false;
}

bool Footprint::IsSelectedInArea(int x1, int y1, int x2, int y2, bool touch_is_enough)
{
	BoardRect sr = BoardRect(x1, y1, x2-x1, y2-y1);
	if(m_graphics.GetObjects().empty() && m_block_area.GetObjects().empty()){
		BoardRect r(-DEFAULT_RECT_W/2-wxGetApp().m_settings.m_linewidth_footprint_graphics/2,
				-DEFAULT_RECT_H/2-wxGetApp().m_settings.m_linewidth_footprint_graphics/2,
				DEFAULT_RECT_W+wxGetApp().m_settings.m_linewidth_footprint_graphics,
				DEFAULT_RECT_H+wxGetApp().m_settings.m_linewidth_footprint_graphics);
		if(touch_is_enough){
			if(r.Collides(sr)) return true;
		}
		else{
			if(sr.Contains(r)) return true;
		}
	}
	if(m_block_area.GetObjects().empty()){
		for(std::list<JukuObject*>::const_iterator i = m_graphics.GetObjects().begin(); i != m_graphics.GetObjects().end(); i++){
			if((*i)->GetType() == TYPE_RECT){
				BoardRect *r = (BoardRect*)((Rect*)(*i));
				if(touch_is_enough){
					if(r->Collides(sr)) return true;
				}
				else{
					if(sr.Contains(*r)) return true;
				}
			}
			if((*i)->GetType() == TYPE_ARC){
				JArc *arc = (JArc*)(*i);
				int r_max, t;
				r_max = sqrt(pow(arc->m_x1 - arc->m_xc, 2) + pow(arc->m_y1 - arc->m_yc, 2));
				t = sqrt(pow(arc->m_x2 - arc->m_xc, 2) + pow(arc->m_y2 - arc->m_yc, 2));
				if(t > r_max) r_max = t;
				BoardRect r = BoardRect(arc->m_xc-r_max, arc->m_yc-r_max, r_max*2, r_max*2);
				if(touch_is_enough){
					if(r.Collides(sr)) return true;
				}
				else{
					if(sr.Contains(r)) return true;
				}
			}
		}
		return false;
	}
	for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
		if((*i)->GetType() == TYPE_RECT){
			BoardRect *r = (BoardRect*)((Rect*)(*i));
			if(touch_is_enough){
				if(r->Collides(sr)) return true;
			}
			else{
				if(sr.Contains(*r)) return true;
			}
		}
	}
	return false;
}

void Footprint::SetSelected(bool selected)
{
	m_graphics.SetSelected(selected);
	m_block_area.SetSelected(selected);
	m_pins.SetSelected(selected);
	JukuObject::SetSelected(selected);
}

void Footprint::Rotate(TurnDirection dir, BoardPoint origin)
{
	for(std::list<JukuObject*>::const_iterator i = m_graphics.GetObjects().begin(); i != m_graphics.GetObjects().end(); i++){
		(*i)->Rotate(dir, origin);
	}
	for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
		(*i)->Rotate(dir, origin);
	}
	for(std::list<JukuObject*>::const_iterator i = m_pins.GetObjects().begin(); i != m_pins.GetObjects().end(); i++){
		(*i)->Rotate(dir, origin);
	}
}

void Footprint::Move(int dx, int dy)
{
	for(std::list<JukuObject*>::const_iterator i = m_graphics.GetObjects().begin(); i != m_graphics.GetObjects().end(); i++){
		(*i)->Move(dx, dy);
	}
	for(std::list<JukuObject*>::const_iterator i = m_block_area.GetObjects().begin(); i != m_block_area.GetObjects().end(); i++){
		(*i)->Move(dx, dy);
	}
	for(std::list<JukuObject*>::const_iterator i = m_pins.GetObjects().begin(); i != m_pins.GetObjects().end(); i++){
		(*i)->Move(dx, dy);
	}
}

/*void Footprint::Rotate180()
{
	if(m_pins.GetObjects().empty()) return;
	BoardRect r = GetPinExtents();
	int center_x = r.m_x + r.m_w/2;
	int center_y = r.m_y + r.m_h/2;
	BoardPoint center(center_x, center_y);
	Rotate(CCW, center);
	Rotate(CCW, center);
}*/

BoardRect Footprint::GetPinExtents()
{
	int x1=BIG_INT_VALUE, y1=BIG_INT_VALUE, x2=SMALL_INT_VALUE, y2=SMALL_INT_VALUE;
	for(std::list<JukuObject*>::const_iterator i = m_pins.GetObjects().begin(); i != m_pins.GetObjects().end(); i++){
		if((*i)->GetType() != TYPE_PIN) continue;
		Pin *pin = (Pin*)(*i);
		if(pin->m_x < x1) x1 = pin->m_x;
		if(pin->m_y < y1) y1 = pin->m_y;
		if(pin->m_x > x2) x2 = pin->m_x;
		if(pin->m_y > y2) y2 = pin->m_y;
	}
	if(x1 == BIG_INT_VALUE) return BoardRect(0,0,0,0);
	return BoardRect(x1, y1, x2-x1, y2-y1);
}

void Footprint::SimplifyCoordinates(BoardPoint *masterpos)
{
	BoardRect r = GetPinExtents();
	int dx = -r.m_x;
	int dy = -r.m_y;
	Move(dx, dy);
	if(masterpos){
		masterpos->m_x -= dx;
		masterpos->m_y -= dy;
	}
}

//functions

Footprint* MakeTwoWireFootprint(FootprintType type, int length, int size)
{
	Footprint *f = new Footprint();
	if(!f) return NULL;

	f->m_side = TOP;

	//just some dummy values for error robustness
	int e1 = length/4;
	int e2 = 3*length/4;
	bool draw_lines = true;

	wxString pin1_name(wxT("1"));
	wxString pin2_name(wxT("2"));
	int pin1_mark = 0;
	int pin2_mark = 0;

	switch(type)
	{
	case FOOTPRINT_UNKNOWN:
		assert(0);
		break;
	case FOOTPRINT_ACY:
		{
			f->m_name << wxT("ACY") << length;
			
			f->m_subname = wxT("gen.");
	
			if(length <= 200){
				JArc *a = new JArc();
				a->m_xc = 0;
				a->m_yc = 0;
				a->m_x1 = 0;
				a->m_x2 = 0;
				a->m_y1 = -45;
				a->m_y2 = -45;
				f->m_graphics.Add(a, NULL);

				Line *l = new Line();
				l->m_x1 = 0;
				l->m_x2 = 0;
				l->m_y1 = 0;
				l->m_y2 = length;
				f->m_graphics.Add(l, NULL);
				
				draw_lines = false;
			}
			else{
				e1 = (length-236)/2;
				e2 = length - e1;

				BoardRect br;
				br.m_x = -45;
				br.m_y = e1;
				br.m_w = 90;
				br.m_h = e2-e1;
				Rect *r = new Rect(br);
				f->m_graphics.Add(r, NULL);
			}
			
			BoardRect br;
			br.m_x = -50;
			br.m_y = -50;
			br.m_w = 100;
			br.m_h = length+100;
			Rect *r = new Rect(br);
			f->m_block_area.Add(r, NULL);
		}
		break;
	case FOOTPRINT_ALF:
		{
			f->m_name << wxT("ALF") << length;
			
			f->m_subname = wxT("gen.");

			pin1_mark = 1;
	
			e1 = (length-80)/2 - 10;
			e2 = length - e1 + 10;

			Line *l;

			l = new Line();
			l->m_x1 = -50;
			l->m_x2 = 50;
			l->m_y1 = e1+10;
			l->m_y2 = e1+10;
			f->m_graphics.Add(l, NULL);

			l = new Line();
			l->m_x1 = -50;
			l->m_x2 = 0;
			l->m_y1 = e2-10;
			l->m_y2 = e1+10;
			f->m_graphics.Add(l, NULL);

			l = new Line();
			l->m_x1 = 50;
			l->m_x2 = 0;
			l->m_y1 = e2-10;
			l->m_y2 = e1+10;
			f->m_graphics.Add(l, NULL);

			l = new Line();
			l->m_x1 = -50;
			l->m_x2 = 50;
			l->m_y1 = e2-10;
			l->m_y2 = e2-10;
			f->m_graphics.Add(l, NULL);
			
			BoardRect br;
			br.m_x = -50;
			br.m_y = -50;
			br.m_w = 100;
			br.m_h = length+100;
			Rect *r = new Rect(br);
			f->m_block_area.Add(r, NULL);
		}
		break;
	case FOOTPRINT_ACC:
		{
			f->m_name << wxT("ACC") << length;
			
			f->m_subname = wxT("gen.");
	
			e1 = (length-120)/2;
			e2 = length - e1;

			JArc *a;
			
			a = new JArc();
			a->m_xc = 0;
			a->m_yc = e1;
			a->m_x1 = 40;
			a->m_x2 = -40;
			a->m_y1 = e1;
			a->m_y2 = e1;
			f->m_graphics.Add(a, NULL);

			a = new JArc();
			a->m_xc = 0;
			a->m_yc = e2;
			a->m_x1 = -40;
			a->m_x2 = 40;
			a->m_y1 = e2;
			a->m_y2 = e2;
			f->m_graphics.Add(a, NULL);

			Line *l;

			l = new Line();
			l->m_x1 = -40;
			l->m_x2 = -40;
			l->m_y1 = e1;
			l->m_y2 = e2;
			f->m_graphics.Add(l, NULL);

			l = new Line();
			l->m_x1 = 40;
			l->m_x2 = 40;
			l->m_y1 = e1;
			l->m_y2 = e2;
			f->m_graphics.Add(l, NULL);
			
			BoardRect br;
			br.m_x = -50;
			br.m_y = -50;
			br.m_w = 100;
			br.m_h = length+100;
			Rect *r = new Rect(br);
			f->m_block_area.Add(r, NULL);
		}
		break;
	case FOOTPRINT_RCY:
		{
			f->m_name << wxT("RCY") << size;

			f->m_subname << wxT("L=") << length;
	
			pin1_mark = 1;
	
			e1 = (length-100)/2;
			e2 = length - e1;

			JArc *a = new JArc();
			a->m_xc = 0;
			a->m_yc = length/2;
			a->m_x1 = a->m_xc-128+(size-100);
			a->m_x2 = a->m_xc-128+(size-100);
			a->m_y1 = length/2;
			a->m_y2 = length/2;
			f->m_graphics.Add(a, NULL);
		}
		break;
	}
	
	if(draw_lines){
		Line *l1 = new Line();
		l1->m_x1 = 0;
		l1->m_x2 = 0;
		l1->m_y1 = 0;
		l1->m_y2 = e1;
		f->m_graphics.Add(l1, NULL);

		Line *l2 = new Line();
		l2->m_x1 = 0;
		l2->m_x2 = 0;
		l2->m_y1 = e2;
		l2->m_y2 = length;
		f->m_graphics.Add(l2, NULL);
	}
	
	Pin *p1 = new Pin();
	p1->m_num = pin1_name;
	p1->m_type = PIN_THROUGH_HOLE;
	p1->m_mark = pin1_mark;
	p1->m_x = 0;
	p1->m_y = 0;
	f->m_pins.Add(p1, NULL);

	Pin *p2 = new Pin();
	p2->m_num = pin2_name;
	p2->m_type = PIN_THROUGH_HOLE;
	p2->m_mark = pin2_mark;
	p2->m_x = 0;
	p2->m_y = length;
	f->m_pins.Add(p2, NULL);

	return f;
}

